use crate::error::AppError;
use crate::vo::user::UserReqVo;
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::SaltString;
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use chrono::{DateTime, Local};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, MySqlPool};
use tracing::info;

#[derive(sqlx::FromRow, Serialize, Deserialize, Clone, Debug)]
#[allow(unused)]
pub struct EmailPass {
    pub email: String,
    pub password: String,
}

#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
pub struct User {
    pub id: i64,
    pub name: String,
    pub phone: String,
    pub email: String,
    pub age: i8,
    #[serde(skip)]
    pub password: String,
    pub description: String,
    pub sex: i8,
    pub status: i8,

    #[serde(
        serialize_with = "crate::common::serialize",
        deserialize_with = "crate::common::deserialize"
    )]
    pub create_at: DateTime<Local>,
    #[serde(
        serialize_with = "crate::common::serialize",
        deserialize_with = "crate::common::deserialize"
    )]
    pub update_at: DateTime<Local>,
}

impl User {
    /// find a user by email
    #[allow(unused)]
    pub async fn find_by_email(email: &str, pool: &MySqlPool) -> Result<Option<Self>, AppError> {
        let user = sqlx::query_as("SELECT * FROM user WHERE email = ?;")
            .bind(email)
            .fetch_optional(pool)
            .await?;
        Ok(user)
    }

    #[allow(unused)]
    pub async fn create(user: User, pool: &MySqlPool) -> Result<u64, AppError> {
        let old_user = Self::find_by_email(user.email.as_str(), pool).await?;
        match old_user {
            Some(_old_user) => return Err(AppError::UserHasExists),
            None => {
                info!("user not exists");
            }
        }
        let user = sqlx::query(
            r#"
                    INSERT INTO user (name, phone, email, age, password, description,sex, create_at,update_at) values (?, ?, ?, ?, ?, ?, ?,?,?);
                "#
        ).bind(user.name).bind(user.phone).bind(user.email).bind(user.age).bind(hash_password(&user.password)?).bind(user.description).bind(user.sex).bind(user.create_at).bind(user.update_at)
        .execute(pool).await?;
        Ok(user.last_insert_id())
    }

    #[allow(unused)]
    pub async fn verify_email_and_password(
        email: &str,
        password: &str,
        pool: &MySqlPool,
    ) -> Result<Option<Self>, AppError> {
        let user = Self::find_by_email(email, pool).await?;
        match user {
            Some(user) => {
                let is_valid = verify_password(password, &user.password)?;
                if is_valid {
                    Ok(Some(user))
                } else {
                    Ok(None)
                }
            }
            None => Ok(None),
        }
    }
}

#[allow(unused)]
fn hash_password(password: &str) -> Result<String, AppError> {
    let salt = SaltString::generate(&mut OsRng);

    // Argon2 with default params (Argon2id v19)
    let argon2 = Argon2::default();

    // Hash password to PHC string ($argon2id$v=19$...)
    let password_hash = argon2.hash_password(password.as_ref(), &salt)?.to_string();

    // let parsed_hash = PasswordHash::new(&password_hash)?;
    // assert!(Argon2::default().verify_password(password, &parsed_hash).is_ok());
    Ok(password_hash)
}

#[allow(unused)]
fn verify_password(password: &str, hashed_password: &str) -> Result<bool, AppError> {
    let parsed_hash = PasswordHash::new(hashed_password)?;
    Ok(Argon2::default()
        .verify_password(password.as_ref(), &parsed_hash)
        .is_ok())
}

impl From<UserReqVo> for User {
    fn from(user: UserReqVo) -> Self {
        User {
            id: Default::default(),
            name: user.name,
            phone: user.phone,
            age: user.age,
            sex: user.sex,
            email: user.email,
            password: user.password,
            description: user.description,
            status: 1,
            create_at: Default::default(),
            update_at: Default::default(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    // use sqlx::mysql::MySqlPoolOptions;
    use sqlx::MySqlPool;

    #[sqlx::test]
    async fn it_connects_without_password(pool: MySqlPool) -> anyhow::Result<()> {
        // let pool: MySqlPool = MySqlPoolOptions::new()
        //     .min_connections(2)
        //     .max_connections(2)
        //     .test_before_acquire(false)
        //     .connect("mysql://root:123456@127.0.0.1:3306/chat")
        //     .await?;

        let user_req = User {
            id: 0,
            name: "abc".to_string(),
            phone: "cde".to_string(),
            email: "cdefssssssf@qq.com".to_string(),
            age: 0,
            password: "123456".to_string(),
            description: "123".to_string(),
            sex: 1,
            status: 1,
            create_at: Local::now(),
            update_at: Local::now(),
        };
        let res = User::create(user_req, &pool).await;
        match res {
            Ok(_) => {
                println!("user created successfully");
            }
            Err(e) => {
                println!("user created failed: {e}");
            }
        }
        Ok(())
    }

    #[sqlx::test]
    async fn test_email_and_password(pool: MySqlPool) -> anyhow::Result<()> {
        // let pool: MySqlPool = MySqlPoolOptions::new()
        //     .min_connections(2)
        //     .max_connections(2)
        //     .test_before_acquire(false)
        //     .connect("mysql://root:123456@127.0.0.1:3306/chat")
        //     .await?;

        let user = User::find_by_email("cde@qq.com", &pool).await?;
        match user {
            Some(user) => {
                println!("{user:?}");
            }
            None => {
                println!("User not found");
            }
        }
        Ok(())
    }
}
